<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, viewport-fit=cover"/>
<meta name="description" content="Limit the dragging of nodes to avoid any overlap with other nodes."/> 
<link rel="stylesheet" href="../assets/css/style.css"/> 
<!-- Copyright 1998-2021 by Northwoods Software Corporation. -->
<title>Drag Unoccupied</title>
</head>

<body>
  <!-- This top nav is not part of the sample code -->
  <nav id="navTop" class="w-full z-30 top-0 text-white bg-nwoods-primary">
    <div class="w-full container max-w-screen-lg mx-auto flex flex-wrap sm:flex-nowrap items-center justify-between mt-0 py-2">
      <div class="md:pl-4">
        <a class="text-white hover:text-white no-underline hover:no-underline
        font-bold text-2xl lg:text-4xl rounded-lg hover:bg-nwoods-secondary " href="../">
          <h1 class="mb-0 p-1 ">GoJS</h1>
        </a>
      </div>
      <button id="topnavButton" class="rounded-lg sm:hidden focus:outline-none focus:ring" aria-label="Navigation">
        <svg fill="currentColor" viewBox="0 0 20 20" class="w-6 h-6">
          <path id="topnavOpen" fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z" clip-rule="evenodd"></path>
          <path id="topnavClosed" class="hidden" fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
        </svg>
      </button>
      <div id="topnavList" class="hidden lg:text-base sm:block items-center w-auto mt-0 text-white p-0 z-20">
        <ul class="list-reset list-none font-semibold flex justify-end flex-wrap sm:flex-nowrap items-center px-0 pb-0">
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../learn/">Learn</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../samples/">Samples</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../intro/">Intro</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../api/">API</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/products/register.html">Register</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="../download.html">Download</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://forum.nwoods.com/c/gojs/11">Forum</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/contact.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/contact.html', 'contact');">Contact</a></li>
          <li class="p-1 sm:p-0"><a class="topnav-link" href="https://www.nwoods.com/sales/index.html"
           target="_blank" rel="noopener" onclick="getOutboundLink('https://www.nwoods.com/sales/index.html', 'buy');">Buy</a></li>
        </ul>
      </div>
    </div>
    <hr class="border-b border-gray-600 opacity-50 my-0 py-0" />
  </nav>
  <div class="md:flex flex-col md:flex-row md:min-h-screen w-full max-w-screen-xl mx-auto">
    <div id="navSide" class="flex flex-col w-full md:w-48 text-gray-700 bg-white flex-shrink-0"></div>
    <!-- * * * * * * * * * * * * * -->
    <!-- Start of GoJS sample code -->
    
    <script src="../release/go.js"></script>
    <div class="p-4 w-full">
    <script id="code">
    function init() {
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      // R is a Rect in document coordinates
      // NODE is the Node being moved -- ignore when looking for Parts intersecting the Rect
      function isUnoccupied(r, node) {
        var diagram = node.diagram;

        // nested function used by Layer.findObjectsIn, below
        // only consider Parts, and ignore the given Node, any Links, and Group members
        function navig(obj) {
          var part = obj.part;
          if (part === node) return null;
          if (part instanceof go.Link) return null;
          if (part.isMemberOf(node)) return null;
          if (node.isMemberOf(part)) return null;
          return part;
        }

        // only consider non-temporary Layers
        var lit = diagram.layers;
        while (lit.next()) {
          var lay = lit.value;
          if (lay.isTemporary) continue;
          if (lay.findObjectsIn(r, navig, null, true).count > 0) return false;
        }
        return true;
      }

      // a Part.dragComputation function that prevents a Part from being dragged to overlap another Part
      // use PT instead of GRIDPT if DraggingTool.isGridSnapEnabled but movement should not snap to grid
      function avoidNodeOverlap(node, pt, gridpt) {
        if (node.diagram instanceof go.Palette) return gridpt;
        // this assumes each node is fully rectangular
        var bnds = node.actualBounds;
        var loc = node.location;
        // use PT instead of GRIDPT if you want to ignore any grid snapping behavior
        // see if the area at the proposed location is unoccupied
        var r = new go.Rect(gridpt.x - (loc.x - bnds.x), gridpt.y - (loc.y - bnds.y), bnds.width, bnds.height);
        // maybe inflate R if you want some space between the node and any other nodes
        r.inflate(-0.5, -0.5);  // by default, deflate to avoid edge overlaps with "exact" fits
        // when dragging a node from another Diagram, choose an unoccupied area
        if (!(node.diagram.currentTool instanceof go.DraggingTool) &&
          (!node._temp || !node.layer.isTemporary)) {  // in Temporary Layer during external drag-and-drop
          node._temp = true;  // flag to avoid repeated searches during external drag-and-drop
          while (!isUnoccupied(r, node)) {
            r.x += 10;  // note that this is an unimaginative search algorithm --
            r.y += 2;  // you can improve the search here to be more appropriate for your app
          }
          r.inflate(0.5, 0.5);  // restore to actual size
          // return the proposed new location point
          return new go.Point(r.x - (loc.x - bnds.x), r.y - (loc.y - bnds.y));
        }
        if (isUnoccupied(r, node)) return gridpt;  // OK
        return loc;  // give up -- don't allow the node to be moved to the new location
      }

      myDiagram =
        $(go.Diagram, "myDiagramDiv",
          {
            "animationManager.isEnabled": false,
            "undoManager.isEnabled": true
          });

      // Define the template for Nodes, just some text inside a colored rectangle
      myDiagram.nodeTemplate =
        $(go.Node, "Auto",
          { dragComputation: avoidNodeOverlap },
          { minSize: new go.Size(50, 20), resizable: true },
          new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
          new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify),
          // temporarily put selected nodes in Foreground layer
          new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(),
          $(go.Shape, "Rectangle",
            new go.Binding("fill", "color")),
          $(go.TextBlock,
            new go.Binding("text", "color"))
        );

      myDiagram.model = new go.GraphLinksModel([
        { pos: "-30 0", size: "50 300", color: go.Brush.randomColor() },
        { pos: "120 20", size: "300 50", color: go.Brush.randomColor() },
        { pos: "100 200", size: "300 50", color: go.Brush.randomColor() },
        { pos: "500 50", size: "50 300", color: go.Brush.randomColor() },
        { key: 1, pos: "100 100", size: "50 50", color: "gray" },
        { key: 2, pos: "200 140", size: "50 50", color: "gray" }
      ]);

      myDiagram.findNodeForKey(1).isSelected = true;

      // initialize the Palette that is on the left side of the page
      myPalette =
        $(go.Palette, "myPaletteDiv",  // must name or refer to the DIV HTML element
          {
            nodeTemplateMap: myDiagram.nodeTemplateMap,  // share the templates used by myDiagram
            model: new go.GraphLinksModel([  // specify the contents of the Palette
              { size: "50 50", color: go.Brush.randomColor() },
              { size: "60 40", color: go.Brush.randomColor() }
            ])
          });
    }
    window.addEventListener('DOMContentLoaded', init);
  </script>

<div id="sample">
  <div style="width: 100%; display: flex; justify-content: space-between">
    <div id="myPaletteDiv" style="width: 100px; background-color: floralwhite; border: solid 1px black; margin-right: 2px"></div>
    <div id="myDiagramDiv" style="background-color: white; border: solid 1px black; width: 100%; height: 400px"></div>
  </div>
  <p>
    Drag a node around.
    Notice how you cannot force the dragged node to overlap any other (stationary) node.
    If you drag more than one node, notice how the relative positions of the dragged nodes are maintained
    except when forced to be shifted in order to avoid overlapping other nodes.
  </p>
  <p>
    This functionality is implemented by a custom <a>Part.dragComputation</a> property function,
    which affects how the <a>DraggingTool</a> can move selected nodes.
    You will want to adjust how it finds an empty spot for the dragged node when dragging from another Diagram.
  </p>
</div>
    </div>
    <!-- * * * * * * * * * * * * * -->
    <!--  End of GoJS sample code  -->
  </div>
</body>
<!--  This script is part of the gojs.net website, and is not needed to run the sample -->
<script src="../assets/js/goSamples.js"></script>
</html>
